import chiSquaredDistributionTable from "./chi_squared_distribution_table.js";
import mean from "./mean.js";

/**
 * [χ2 (卡方) 拟合优度检验](http://en.wikipedia.org/wiki/Goodness_of_fit#Pearson.27s_chi-squared_test)
 * 使用一个拟合优度的度量，该度量是观测频率与期望频率之间差异的平方和，
 * 并除以期望的观测数。由此产生的 χ2 统计量 `chiSquared` 可以与卡方分布进行比较，
 * 以确定拟合优度。为了确定卡方分布的自由度，取观测频率的总数并减去估计参数的数量。
 * 检验统计量近似服从自由度为 (k − c) 的卡方分布，其中 `k` 是非空单元格的数量，
 * `c` 是分布中估计参数的数量。
 *
 * @param {Array<number>} data 数据样本
 * @param {Function} distributionType 返回分布在某一点的函数：
 * 例如二项分布、伯努利分布或泊松分布
 * @param {number} significance 显著性水平
 * @returns {number} 卡方拟合优度检验结果
 * @example
 * // 数据来自 William W. Hines 和 Douglas C. Montgomery 的《工程与管理科学中的概率与统计》中的泊松拟合优度示例 10-19
 * var data1019 = [
 *     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 *     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 *     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 *     2, 2, 2, 2, 2, 2, 2, 2, 2,
 *     3, 3, 3, 3
 * ];
 * ss.chiSquaredGoodnessOfFit(data1019, ss.poissonDistribution, 0.05); //= false
 */
function chiSquaredGoodnessOfFit(data, distributionType, significance) {
    // 根据样本数据估计加权平均值
    const inputMean = mean(data);
    // 计算 χ2 统计量的值
    let chiSquared = 0;
    // 假设分布中估计参数的数量，预计会在分布测试中提供。
    // 由于从样本数据中估计 `lambda`，损失一个自由度。
    const c = 1;
    // 假设分布
    // 生成假设分布
    const hypothesizedDistribution = distributionType(inputMean);
    const observedFrequencies = [];
    const expectedFrequencies = [];

    // 创建一个数组，保存样本数据的直方图，形式为 `{ 值: 出现次数 }`
    for (let i = 0; i < data.length; i++) {
        if (observedFrequencies[data[i]] === undefined) {
            observedFrequencies[data[i]] = 0;
        }
        observedFrequencies[data[i]]++;
    }

    // 直方图可能是稀疏的——值之间可能存在间隙。因此我们遍历直方图，
    // 确保间隙处用 0 填充，而不是未定义。
    for (let i = 0; i < observedFrequencies.length; i++) {
        if (observedFrequencies[i] === undefined) {
            observedFrequencies[i] = 0;
        }
    }

    // 创建一个数组，保存给定样本大小和假设分布的预期数据的直方图。
    for (const k in hypothesizedDistribution) {
        if (k in observedFrequencies) {
            expectedFrequencies[+k] = hypothesizedDistribution[k] * data.length;
        }
    }

    // 如果某个类别的期望频率小于 3，则合并类别。
    // 此转换也应用于观测频率。
    for (let k = expectedFrequencies.length - 1; k >= 0; k--) {
        if (expectedFrequencies[k] < 3) {
            expectedFrequencies[k - 1] += expectedFrequencies[k];
            expectedFrequencies.pop();

            observedFrequencies[k - 1] += observedFrequencies[k];
            observedFrequencies.pop();
        }
    }

    // 遍历观测频率与预期频率之间的平方差，累加 `chiSquared` 统计量。
    for (let k = 0; k < observedFrequencies.length; k++) {
        chiSquared +=
            Math.pow(observedFrequencies[k] - expectedFrequencies[k], 2) /
            expectedFrequencies[k];
    }

    // 计算自由度并查表以接受或拒绝假设分布的拟合优度。
    // 自由度计算公式为 (类别区间数 - 估计参数数 - 1)
    const degreesOfFreedom = observedFrequencies.length - c - 1;
    return (
        chiSquaredDistributionTable[degreesOfFreedom][significance] < chiSquared
    );
}

export default chiSquaredGoodnessOfFit;
